package com.itheima.stock.service.impl;

import com.google.common.collect.Lists;
import com.itheima.stock.face.StockCacheFace;
import com.itheima.stock.mapper.*;
import com.itheima.stock.pojo.entity.StockBlockRtInfo;
import com.itheima.stock.pojo.entity.StockMarketIndexInfo;
import com.itheima.stock.pojo.entity.StockOuterMarketIndexInfo;
import com.itheima.stock.pojo.entity.StockRtInfo;
import com.itheima.stock.pojo.vo.StockInfoConfig;
import com.itheima.stock.service.StockTimerTasKService;
import com.itheima.stock.utils.DateTimeUtil;
import com.itheima.stock.utils.IdWorker;
import com.itheima.stock.utils.ParseType;
import com.itheima.stock.utils.ParserStockInfoUtil;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@Service
@Slf4j
public class StockTimerTasKServiceImpl implements StockTimerTasKService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private StockInfoConfig stockInfoConfig;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private StockBusinessMapper stockBusinessMapper;

    @Autowired
    private StockMarketIndexInfoMapper stockMarketIndexInfoMapper;

    @Autowired
    private ParserStockInfoUtil parserStockInfoUtil;

    @Autowired
    private StockRtInfoMapper stockRtInfoMapper;

    @Autowired
    private StockBlockRtInfoMapper stockBlockRtInfoMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private StockOuterMarketIndexInfoMapper stockOuterMarketIndexInfoMapper;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 必须保证该对象无状态，没有什么变化的值
     */
    private HttpEntity<Object> httpEntity;

    @Autowired
    private StockCacheFace stockCacheFace;

    @Override
    public void getInnerMarketInfo() {
        //1.阶段1：采集原始数据
        //1.1组装URL地址
        String url = stockInfoConfig.getMarketUrl()+String.join(",",stockInfoConfig.getInner());
//        //1.2维护请求头，添加防盗链和用户标识
//        HttpHeaders headers = new HttpHeaders();
//        //防盗链
//        headers.add("Referer","https://finance.sina.com.cn/");
//        //用户客户端标识
//        headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0");
//        //维护http请求实体对象
//        HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
        int statusCodeValue = responseEntity.getStatusCodeValue();
        if (statusCodeValue!=200) {
            //当前请求失败
            log.error("当前时间点：{},采集数据失败,http状态码：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),statusCodeValue);
            //其它：发送邮件 企业微信 钉钉灯给相关运营人员提醒
            return;
        }
        //获取js格式数据
        String jsData = responseEntity.getBody();
        log.error("当前时间点：{},采集的原始数据的内容：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),jsData);
        //2.阶段2：java正则解析原始数据
        //2.1定义正则表达式
        String reg = "var hq_str_(.+)=\"(.+)\";";
        //2.2表达式编译
        Pattern pattern = Pattern.compile(reg);
        //2.3 匹配字符串
        Matcher matcher = pattern.matcher(jsData);
        List<StockMarketIndexInfo> entities = new ArrayList<>();
        while (matcher.find()){
            //1.获取大盘的编码
            String marketCode = matcher.group(1);
            //2.获取其他信息
            String otherInfo = matcher.group(2);
            //将otherInfo字符串以逗号切割，获取大片的详情信息
            String[] splitArr = otherInfo.split(",");
            //大盘名称
            String marketName=splitArr[0];
            //获取当前大盘的开盘点数
            BigDecimal openPoint=new BigDecimal(splitArr[1]);
            //前收盘点
            BigDecimal preClosePoint=new BigDecimal(splitArr[2]);
            //获取大盘的当前点数
            BigDecimal curPoint=new BigDecimal(splitArr[3]);
            //获取大盘最高点
            BigDecimal maxPoint=new BigDecimal(splitArr[4]);
            //获取大盘的最低点
            BigDecimal minPoint=new BigDecimal(splitArr[5]);
            //获取成交量
            Long tradeAmt=Long.valueOf(splitArr[8]);
            //获取成交金额
            BigDecimal tradeVol=new BigDecimal(splitArr[9]);
            //时间
            Date curTime = DateTimeUtil.getDateTimeWithoutSecond(splitArr[30] + " " + splitArr[31]).toDate();
            //3.阶段3：解析数据封装entity
            //组装entity对象
            StockMarketIndexInfo info = StockMarketIndexInfo.builder()
                    .id(idWorker.nextId())
                    .marketCode(marketCode)
                    .marketName(marketName)
                    .curPoint(curPoint)
                    .openPoint(openPoint)
                    .preClosePoint(preClosePoint)
                    .maxPoint(maxPoint)
                    .minPoint(minPoint)
                    .tradeVolume(tradeVol)
                    .tradeAmount(tradeAmt)
                    .curTime(curTime)
                    .build();
            //收集封装的对象，方便批量插入
            entities.add(info);
        }
        log.info("解析数据完毕");
        //4.阶段4：调用mybatis批量入库
        int count = stockMarketIndexInfoMapper.insertBatch(entities);
        if(count>0){
            //大盘数据采集完毕后，通知backend工程刷新缓存
            //发送日期对象，接受方通过接收的日期与当前日期比对，能判断出数据延迟的时长，用于运维通知处理
            rabbitTemplate.convertAndSend("stockExchange","inner.market",new Date());
            log.info("当前时间：{},插入数据：{}成功",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),entities);
        }else{
            log.error("当前时间：{},插入数据：{}失败",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),entities);
        }
    }

    /**
     * 定义获取分钟级股票数据
     */
    @Override
    public void getStockRtIndex() {
        //1.获取所有个股的集合 3000+
        List<String> allStockCode = stockCacheFace.getAllStockWithPrefix();
        //将所有个股编码组成的大的集合拆分成若干小的集合 40--->15 15 10
        // List<List<String>> partition = Lists.partition(allStockCode, 15);
        //将大的集合切分成若干小集合，分批次拉去数据
        Lists.partition(allStockCode,15).forEach(codes->{
//            原始方案
//            //分批次采集
//            String url = stockInfoConfig.getMarketUrl()+String.join(",",codes);
////            //1.2维护请求头，添加防盗链和用户标识
////            HttpHeaders headers = new HttpHeaders();
////            //防盗链
////            headers.add("Referer","https://finance.sina.com.cn/");
////            //用户客户端标识
////            headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0");
////            //维护http请求实体对象
////            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
//            //发送请求
//            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
//            int statusCodeValue = responseEntity.getStatusCodeValue();
//            if (statusCodeValue!=200) {
//                //当前请求失败
//                log.error("当前时间点：{},采集数据失败,http状态码：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),statusCodeValue);
//                //其它：发送邮件 企业微信 钉钉灯给相关运营人员提醒
//                return;
//            }
//            //获取原始js格式数据
//            String jsData = responseEntity.getBody();
//            //调用工具类解析获取各个数据
//            List<StockRtInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(jsData, ParseType.ASHARE);
//            log.info("采集个股数据：{}",list);
//            //批量插入
//            int count = stockRtInfoMapper.insertBatch(list);
//            if(count>0){
//                log.info("当前时间：{},插入数据：{}成功",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),list);
//            }else{
//                log.error("当前时间：{},插入数据：{}失败",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),list);
//            }
            //方案1：原始方案采集个股数据时将集合分片，然后分批次穿行采集数据，效率不高，存在较高得采集延迟：引入多线程
            //代码的问题：1.每次来任务，就创建一个线程，复用性差 2.如果多线程使用不当，造成CPU竞争激烈，导致频繁的上下文切换，导致程序性能降低
//            new Thread(()->{
//            //分批次采集
//                String url = stockInfoConfig.getMarketUrl()+String.join(",",codes);
//    //            //1.2维护请求头，添加防盗链和用户标识
//    //            HttpHeaders headers = new HttpHeaders();
//    //            //防盗链
//    //            headers.add("Referer","https://finance.sina.com.cn/");
//    //            //用户客户端标识
//    //            headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0");
//    //            //维护http请求实体对象
//    //            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
//                //发送请求
//                ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
//                int statusCodeValue = responseEntity.getStatusCodeValue();
//                if (statusCodeValue!=200) {
//                    //当前请求失败
//                    log.error("当前时间点：{},采集数据失败,http状态码：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),statusCodeValue);
//                    //其它：发送邮件 企业微信 钉钉灯给相关运营人员提醒
//                    return;
//                }
//                //获取原始js格式数据
//                String jsData = responseEntity.getBody();
//                //调用工具类解析获取各个数据
//                List<StockRtInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(jsData, ParseType.ASHARE);
//                log.info("采集个股数据：{}",list);
//                //批量插入
//                int count = stockRtInfoMapper.insertBatch(list);
//                if(count>0){
//                    log.info("当前时间：{},插入数据：{}成功",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),list);
//                }else{
//                    log.error("当前时间：{},插入数据：{}失败",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),list);
//                }
//            }).start();

            //方案2：引入线程池
            threadPoolTaskExecutor.execute(()->{
                //分批次采集
                String url = stockInfoConfig.getMarketUrl()+String.join(",",codes);
                //            //1.2维护请求头，添加防盗链和用户标识
                //            HttpHeaders headers = new HttpHeaders();
                //            //防盗链
                //            headers.add("Referer","https://finance.sina.com.cn/");
                //            //用户客户端标识
                //            headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0");
                //            //维护http请求实体对象
                //            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
                //发送请求
                ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
                int statusCodeValue = responseEntity.getStatusCodeValue();
                if (statusCodeValue!=200) {
                    //当前请求失败
                    log.error("当前时间点：{},采集数据失败,http状态码：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),statusCodeValue);
                    //其它：发送邮件 企业微信 钉钉灯给相关运营人员提醒
                    return;
                }
                //获取原始js格式数据
                String jsData = responseEntity.getBody();
                //调用工具类解析获取各个数据
                List<StockRtInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(jsData, ParseType.ASHARE);
                log.info("采集个股数据：{}",list);
                //批量插入
                int count = stockRtInfoMapper.insertBatch(list);
                if(count>0){
                    log.info("当前时间：{},插入数据：{}成功",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),list);
                }else{
                    log.error("当前时间：{},插入数据：{}失败",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),list);
                }
            });

        });
    }

    @Override
    public void getBlockRtInfo() {
        String url = stockInfoConfig.getBlockUrl();
        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
        //获取原始js格式数据
        String jsData = responseEntity.getBody();
        //调用工具类解析获取各个数据
        List<StockBlockRtInfo> stockBlockRtInfos = parserStockInfoUtil.parse4StockBlock(jsData);
        log.info("采集板块数据：{}",stockBlockRtInfos);
        //批量插入
        int count = stockBlockRtInfoMapper.insertBatch(stockBlockRtInfos);
        if(count>0){
            log.info("当前时间：{},插入数据：{}成功",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),stockBlockRtInfos);
        }else{
            log.error("当前时间：{},插入数据：{}失败",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),stockBlockRtInfos);
        }

    }

    @Override
    public void getOuterMarketIndexInfo() {
        //1.阶段1：采集原始数据
        //1.1组装URL地址
        String url = stockInfoConfig.getMarketUrl()+String.join(",",stockInfoConfig.getOuter());
        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
        int statusCodeValue = responseEntity.getStatusCodeValue();
        if (statusCodeValue!=200) {
            //当前请求失败
            log.error("当前时间点：{},采集数据失败,http状态码：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),statusCodeValue);
            //其它：发送邮件 企业微信 钉钉灯给相关运营人员提醒
            return;
        }
        //获取js格式数据
        String jsData = responseEntity.getBody();
        log.error("当前时间点：{},采集的原始数据的内容：{}", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),jsData);
        //2.阶段2：java正则解析原始数据
        //2.1定义正则表达式
        String reg = "var hq_str_(.+)=\"(.+)\";";
        //2.2表达式编译
        Pattern pattern = Pattern.compile(reg);
        //2.3 匹配字符串
        Matcher matcher = pattern.matcher(jsData);
        List<StockOuterMarketIndexInfo> entities = new ArrayList<>();
        while (matcher.find()){
            //1.获取大盘的编码
            String marketCode = matcher.group(1);
            //2.获取其他信息
            String otherInfo = matcher.group(2);
            //将otherInfo字符串以逗号切割，获取大片的详情信息
            String[] splitArr = otherInfo.split(",");
            //外盘名称
            String marketName=splitArr[0];
            //获取当前外盘的点数
            BigDecimal curPoint=new BigDecimal(splitArr[1]);
            //涨跌值
            BigDecimal updown=new BigDecimal(splitArr[2]);
            //涨幅
            BigDecimal rose=new BigDecimal(splitArr[3]);
            //时间
            Date curTime = new Date();
            //3.阶段3：解析数据封装entity
            //组装entity对象
            StockOuterMarketIndexInfo info = StockOuterMarketIndexInfo.builder()
                    .id(idWorker.nextId())
                    .marketCode(marketCode)
                    .marketName(marketName)
                    .curPoint(curPoint)
                    .updown(updown)
                    .rose(rose)
                    .curTime(curTime)
                    .build();
            //收集封装的对象，方便批量插入
            entities.add(info);
        }
        log.info("采集外股数据解析数据完毕");
        //4.阶段4：调用mybatis批量入库
        int count = stockOuterMarketIndexInfoMapper.insertBatch(entities);
        if(count>0){
            //大盘数据采集完毕后，通知backend工程刷新缓存
            //发送日期对象，接受方通过接收的日期与当前日期比对，能判断出数据延迟的时长，用于运维通知处理
            rabbitTemplate.convertAndSend("stockExchange","inner.market",new Date());
            log.info("当前时间：{},插入数据：{}成功",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),entities);
        }else{
            log.error("当前时间：{},插入数据：{}失败",DateTime.now().toString("yyyy-MM-dd HH:mm:ss"),entities);
        }
    }


    /**
     * bean生命周期的初始化回调方法
     */
    @PostConstruct
    public void initDate(){
        //维护请求头，添加防盗链和用户标识
        HttpHeaders headers = new HttpHeaders();
        //防盗链
        headers.add("Referer","https://finance.sina.com.cn/");
        //用户客户端标识
        headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0");
        //维护http请求实体对象
        httpEntity = new HttpEntity<>(headers);
    }
}
